package users;

import algos.IVocabulary;
import algos.Vocabulary;
import utils.Common;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

import static utils.Common.newArrayList;
import static utils.Common.newHashSet;
import static utils.Common.newTreeMap;

//WARNING: it relies that Vocabulary doesn't change its size in runtime
//We couldn't use Vocabulary because we need the possibility of adding elements after initialization
@SuppressWarnings("unchecked")
public class EditableVoc implements IVocabulary {


    private class MergeWordSet implements IWordSet {

        final Iterable<Integer>[] iterable;

        public MergeWordSet(Iterable<Integer>... iterable) {
            this.iterable = iterable;
        }


        @Override
        public Iterator<Integer> iterator() {

            return new Iterator<Integer>() {

                void findNext() {
                    while (!iterator.hasNext()) {
                        at++;
                        if (at == iterable.length){
                            break;
                        }
                        iterator = iterable[at].iterator();
                    }

                }

                int at = 0;
                Iterator<Integer> iterator = iterable[0].iterator();


                @Override
                public boolean hasNext() {
                    findNext();
                    return iterator.hasNext();
                }

                @Override
                public Integer next() {
                    return iterator.next();
                }

                @Override
                public void remove() {
                    throw new UnsupportedOperationException();
                }
            };
        }

    }


    private final Vocabulary baseVoc;

    private int freeId;

    private NavigableMap<String, Integer> userWordsToIndex = newTreeMap();

    private List<String> indexToString = newArrayList();

    private Set<Integer> userWords;

    public EditableVoc(Vocabulary baseVoc) {
        this.baseVoc = baseVoc;
        userWords = newHashSet();
        freeId = baseVoc.size();
    }

    @Override
    public void addWord(String word) {
        if (Vocabulary.isWord(word)) {
            int ind = baseVoc.getIndexFor(word);
            if (ind != NOT_FOUND) {
                userWords.add(ind);
                return;
            }
            if (!userWordsToIndex.containsKey(word)) {
                userWordsToIndex.put(word, freeId);
                indexToString.add(word);
                freeId++;
                userWords.add(freeId);
            }

        }
    }


    @Override
    public int getIndexFor(String word) {
        int baseInd = baseVoc.getIndexFor(word);
        if (baseInd != NOT_FOUND) {
            return baseInd;
        }
        return userWordsToIndex.containsKey(word) ? userWordsToIndex.get(word) : NOT_FOUND;
    }

    @Override
    public IWordSet getWordsWithPrefix(String prefix) {
        Iterable<Integer> wordsWithPrefix = userWordsToIndex.subMap(prefix, true, prefix + Character.MAX_VALUE, false).values();
        return new MergeWordSet(baseVoc.getWordsWithPrefix(prefix), wordsWithPrefix);
    }

    @Override
    public String getWordAt(int index) {
        if (baseVoc.size() > index) {
            return baseVoc.getWordAt(index);
        }
        return indexToString.get(index - baseVoc.size());
    }

    @Override
    public int size() {
        return userWords.size();
    }


    //TODO: copy pasted >_< Move to some abstract class
    public void addWordsFromFile(File file) {
        try {
            BufferedReader br = null;
            try {
                br = new BufferedReader(new FileReader(file));
                while (true) {
                    String line = br.readLine();
                    if (line == null) break;
                    StringTokenizer tokenizer = new StringTokenizer(line);
                    while (tokenizer.hasMoreTokens()) {
                        addWord(tokenizer.nextToken());
                    }
                }
            } finally {
                br.close();
            }
        } catch (IOException e) {
        }
    }

}
